/*
 * Copyright (C) 2020 Graylog, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the Server Side Public License, version 1,
 * as published by MongoDB, Inc.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * Server Side Public License for more details.
 *
 * You should have received a copy of the Server Side Public License
 * along with this program. If not, see
 * <http://www.mongodb.com/licensing/server-side-public-license>.
 */
package org.graylog2.telemetry.rest;

import com.codahale.metrics.MetricRegistry;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.eventbus.EventBus;
import org.apache.commons.collections4.IteratorUtils;
import org.graylog.testing.mongodb.MongoDBExtension;
import org.graylog2.cluster.leader.LeaderElectionService;
import org.graylog2.cluster.nodes.NodeService;
import org.graylog2.database.MongoCollections;
import org.graylog2.indexer.cluster.ClusterAdapter;
import org.graylog2.plugin.PluginMetaData;
import org.graylog2.plugin.ServerStatus;
import org.graylog2.plugin.cluster.ClusterConfigService;
import org.graylog2.plugin.database.users.User;
import org.graylog2.plugin.lifecycles.Lifecycle;
import org.graylog2.plugin.system.SimpleNodeId;
import org.graylog2.shared.bindings.providers.ObjectMapperProvider;
import org.graylog2.shared.system.stats.StatsService;
import org.graylog2.shared.users.UserService;
import org.graylog2.storage.SearchVersion;
import org.graylog2.system.traffic.TrafficCounterService;
import org.graylog2.telemetry.cluster.TelemetryClusterService;
import org.graylog2.telemetry.cluster.db.DBTelemetryClusterInfo;
import org.graylog2.telemetry.user.db.DBTelemetryUserSettingsService;
import org.joda.time.DateTimeZone;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;

import java.time.Duration;
import java.util.Set;

import static org.assertj.core.api.Assertions.assertThat;
import static org.graylog2.telemetry.rest.TelemetryTestHelper.CLUSTER;
import static org.graylog2.telemetry.rest.TelemetryTestHelper.CURRENT_USER;
import static org.graylog2.telemetry.rest.TelemetryTestHelper.DATA_NODES;
import static org.graylog2.telemetry.rest.TelemetryTestHelper.LICENSE;
import static org.graylog2.telemetry.rest.TelemetryTestHelper.PLUGIN;
import static org.graylog2.telemetry.rest.TelemetryTestHelper.SEARCH_CLUSTER;
import static org.graylog2.telemetry.rest.TelemetryTestHelper.USER_TELEMETRY_SETTINGS;
import static org.graylog2.telemetry.rest.TelemetryTestHelper.mockTrafficData;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
@ExtendWith(MongoDBExtension.class)
@MockitoSettings(strictness = Strictness.WARN)
public class TelemetryServiceWithDbTest {

    @Mock
    TrafficCounterService trafficCounterService;
    @Mock
    ClusterConfigService clusterConfigService;
    @Mock
    UserService userService;
    @Mock
    Set<PluginMetaData> pluginMetaDataSet;
    @Mock
    ClusterAdapter elasticClusterAdapter;
    @Mock
    SearchVersion elasticsearchVersion;
    @Mock
    EventBus eventBus;
    @Mock
    User user;
    @Mock
    ServerStatus serverStatus;
    @Mock
    LeaderElectionService leaderElectionService;
    @Mock
    MetricRegistry metricRegistry;
    @Mock
    NodeService nodeService;
    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
    StatsService statsService;

    TelemetryService telemetryService;

    @BeforeEach
    public void setUp(MongoCollections mongoCollections) {
        TelemetryClusterService telemetryClusterService = new TelemetryClusterService(
                serverStatus,
                clusterConfigService,
                leaderElectionService,
                new DBTelemetryClusterInfo(Duration.ZERO, mongoCollections),
                metricRegistry,
                statsService
        );

        telemetryService = new TelemetryService(
                true,
                trafficCounterService,
                userService,
                pluginMetaDataSet,
                elasticClusterAdapter,
                elasticsearchVersion,
                new TelemetryResponseFactory(new ObjectMapperProvider().get()),
                new DBTelemetryUserSettingsService(mongoCollections),
                eventBus,
                telemetryClusterService,
                "unknown",
                nodeService,
                false,
                Set.of());
    }

    @Test
    public void test_no_telemetry_user_settings_present() {
        TelemetryUserSettings telemetryUserSettings = telemetryService.getTelemetryUserSettings(user);

        assertThat(telemetryUserSettings.telemetryEnabled()).isTrue();
        assertThat(telemetryUserSettings.telemetryPermissionAsked()).isFalse();
    }

    @Test
    public void test_save_telemetry_user_settings() {
        saveUserSettings(false);

        TelemetryUserSettings telemetryUserSettings = telemetryService.getTelemetryUserSettings(user);

        assertThat(telemetryUserSettings.telemetryEnabled()).isFalse();
        assertThat(telemetryUserSettings.telemetryPermissionAsked()).isTrue();
    }

    @Test
    public void test_delete_telemetry_user_settings() {
        saveUserSettings(false);

        telemetryService.deleteUserSettingsByUser(user);
        TelemetryUserSettings telemetryUserSettings = telemetryService.getTelemetryUserSettings(user);

        assertThat(telemetryUserSettings.telemetryEnabled()).isTrue();
        assertThat(telemetryUserSettings.telemetryPermissionAsked()).isFalse();
    }

    @Test
    public void test_all_telemetry_data_is_present() {
        mockTrafficData(trafficCounterService);
        when(serverStatus.getNodeId()).thenReturn(new SimpleNodeId("1"));
        when(serverStatus.getLifecycle()).thenReturn(Lifecycle.RUNNING);
        when(serverStatus.getTimezone()).thenReturn(DateTimeZone.UTC);
        when(statsService.systemStats().osStats().memory().total()).thenReturn(-1L);
        when(statsService.systemStats().osStats().processor().totalCores()).thenReturn(-1);

        telemetryService.updateTelemetryClusterData();
        ObjectNode telemetryResponse = telemetryService.getTelemetryResponse(saveUserSettings(true));

        assertThat(IteratorUtils.toList(telemetryResponse.fieldNames())).containsOnly(USER_TELEMETRY_SETTINGS, CURRENT_USER, CLUSTER, LICENSE, PLUGIN, SEARCH_CLUSTER, DATA_NODES);
    }


    private User saveUserSettings(boolean telemetryEnabled) {
        when(user.getId()).thenReturn("id");

        telemetryService.saveUserSettings(user, TelemetryUserSettings.builder()
                .telemetryEnabled(telemetryEnabled)
                .telemetryPermissionAsked(true)
                .build());
        return user;
    }
}
